#include <math.h>

#include "CPU.h"
#include "base/Simulator.h"
#include "base/simapi.h"
#include "util/Logger.h"

#include "config.h"

#if HAVE_LIBOPCODES
#include <dis-asm.h>
#endif

// bdc: based on gdb's "info registers" output
static x86Context oldState[MAX_NUM_CPUS];
inline char* PrintRegisters (char* index, CPU* cpu) {
    bool printAllRegisters = cpu->P->cfg.print_all_registers;
    uint id = cpu->getID();
    ASSERT(global_sim->cfg.num_cpus <= MAX_NUM_CPUS);
    ASSERT(id < global_sim->cfg.num_cpus);
    x86Context* O = oldState + id;

    if (!cpu->P->cfg.print_master ||
        !global_sim->cfg.stats_enabled)
    {
        return index;
    }

    // GPR
    if (printAllRegisters || O->GPR[EAX] != cpu->InterpCtxt.GPR[EAX]) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | eax 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.GPR[EAX], cpu->InterpCtxt.GPR[EAX]);
    }
    if (printAllRegisters || O->GPR[ECX] != cpu->InterpCtxt.GPR[ECX]) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | ecx 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.GPR[ECX], cpu->InterpCtxt.GPR[ECX]);
    }
    if (printAllRegisters || O->GPR[EDX] != cpu->InterpCtxt.GPR[EDX]) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | edx 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.GPR[EDX], cpu->InterpCtxt.GPR[EDX]);
    }
    if (printAllRegisters || O->GPR[EBX] != cpu->InterpCtxt.GPR[EBX]) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | ebx 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.GPR[EBX], cpu->InterpCtxt.GPR[EBX]);
    }
    if (printAllRegisters || O->GPR[ESP] != cpu->InterpCtxt.GPR[ESP]) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | esp 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.GPR[ESP], cpu->InterpCtxt.GPR[ESP]);
    }
    if (printAllRegisters || O->GPR[EBP] != cpu->InterpCtxt.GPR[EBP]) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | ebp 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.GPR[EBP], cpu->InterpCtxt.GPR[EBP]);
    }
    if (printAllRegisters || O->GPR[ESI] != cpu->InterpCtxt.GPR[ESI]) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | esi 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.GPR[ESI], cpu->InterpCtxt.GPR[ESI]);
    }
    if (printAllRegisters || O->GPR[EDI] != cpu->InterpCtxt.GPR[EDI]) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | edi 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.GPR[EDI], cpu->InterpCtxt.GPR[EDI]);
    }

    // IP
    if (printAllRegisters) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | eip 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.IP, cpu->InterpCtxt.IP);
    }

    // eflags
    if (printAllRegisters || O->eflags != cpu->InterpCtxt.eflags) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | eflags 0x"RegFormat" %lld\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.eflags, cpu->InterpCtxt.eflags);
    }

    // SegTable
    if (printAllRegisters || 
        O->SegTable[CS].base  != cpu->InterpCtxt.SegTable[CS].base ||
        O->SegTable[CS].limit != cpu->InterpCtxt.SegTable[CS].limit)
    {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | cs 0x"RegFormat" (limit %lld)\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.SegTable[CS].base, cpu->InterpCtxt.SegTable[CS].limit);
    }
    if (printAllRegisters || 
        O->SegTable[SS].base  != cpu->InterpCtxt.SegTable[SS].base ||
        O->SegTable[SS].limit != cpu->InterpCtxt.SegTable[SS].limit)
    {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | ss 0x"RegFormat" (limit %lld)\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.SegTable[SS].base, cpu->InterpCtxt.SegTable[SS].limit);
    }
    if (printAllRegisters || 
        O->SegTable[DS].base  != cpu->InterpCtxt.SegTable[DS].base ||
        O->SegTable[DS].limit != cpu->InterpCtxt.SegTable[DS].limit)
    {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | ds 0x"RegFormat" (limit %lld)\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.SegTable[DS].base, cpu->InterpCtxt.SegTable[DS].limit);
    }
    if (printAllRegisters || 
        O->SegTable[ES].base  != cpu->InterpCtxt.SegTable[ES].base ||
        O->SegTable[ES].limit != cpu->InterpCtxt.SegTable[ES].limit)
    {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | es 0x"RegFormat" (limit %lld)\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.SegTable[ES].base, cpu->InterpCtxt.SegTable[ES].limit);
    }
    if (printAllRegisters || 
        O->SegTable[FS].base  != cpu->InterpCtxt.SegTable[FS].base ||
        O->SegTable[FS].limit != cpu->InterpCtxt.SegTable[FS].limit)
    {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | fs 0x"RegFormat" (limit %lld)\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.SegTable[FS].base, cpu->InterpCtxt.SegTable[FS].limit);
    }
    if (printAllRegisters || 
        O->SegTable[GS].base  != cpu->InterpCtxt.SegTable[GS].base ||
        O->SegTable[GS].limit != cpu->InterpCtxt.SegTable[GS].limit)
    {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | gs 0x"RegFormat" (limit %lld)\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.SegTable[GS].base, cpu->InterpCtxt.SegTable[GS].limit);
    }
    for (int i = 0; i < X86_NUM_FPR; i++) {
        if (printAllRegisters || (O->FPRshadow[i] != cpu->InterpCtxt.FPRshadow[i] && !(isnan(O->FPRshadow[i]) && isnan(cpu->InterpCtxt.FPRshadow[i])))) {
            index+=sim_sprintf(index, 
                               "%d:%lld:%.16llX REG  | FPR%d %x.%x.%x (shadow 0x%e) (64-bit 0x%e)\n", 
                               id, 
                               global_sim->getSimclock(), 
                               cpu->InterpCtxt.IP, 
                               i, 
                               (uint)cpu->InterpCtxt.FPR[i].high, 
                               (uint)(cpu->InterpCtxt.FPR[i].low >> 32), 
                               (uint)(cpu->InterpCtxt.FPR[i].low && 0xffffffff), 
                               cpu->InterpCtxt.FPRshadow[i],
                               longlong2double(floatx80_to_float64(cpu->InterpCtxt.FPR[i])));
        }
    }
    if (printAllRegisters || O->FPtop != cpu->InterpCtxt.FPtop) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | FPtop 0x%.8X %d\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.FPtop, cpu->InterpCtxt.FPtop);
    }
    if (printAllRegisters || O->FPtag != cpu->InterpCtxt.FPtag) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | FPtag 0x%.8X %d\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.FPtag, cpu->InterpCtxt.FPtag);
    }
    if (printAllRegisters || O->FPstatus != cpu->InterpCtxt.FPstatus) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | FPstatus 0x%.8X %d\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.FPstatus, cpu->InterpCtxt.FPstatus);
    }
    if (printAllRegisters || O->FPcontrol != cpu->InterpCtxt.FPcontrol) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | FPcontrol 0x%.8X %d\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.FPcontrol, cpu->InterpCtxt.FPcontrol);
    }
    if (printAllRegisters || O->SSEscr != cpu->InterpCtxt.SSEscr) {
        index+=sim_sprintf(index, "%d:%lld:%.16llX REG  | SSEscr 0x%.8X %d\n", 
                           id, global_sim->getSimclock(), cpu->InterpCtxt.IP, cpu->InterpCtxt.SSEscr, cpu->InterpCtxt.SSEscr);
    }
    memcpy(O, &cpu->InterpCtxt, sizeof(x86Context));
    return index;
}

#if HAVE_LIBOPCODES
int dis_sprintf (char** bufferPtr, const char* text, ...)
{
    va_list ap;

    va_start(ap,text);
    char* buffer = *bufferPtr;
    int count = sim_vsnprintf(buffer, 4096, text, ap);
    va_end(ap);
    *bufferPtr = buffer+count;
    return count;
}
#endif // HAVE_LIBOPCODES

void CPU::PrintInstruction (Logger* logger,
                            SEVERITY severity,
                            CPU* cpu,
                            bool print_registers,
                            bool print_symbols) {

    const int buffer_size = 16*1024;
    char buffer[buffer_size];
    char* index = buffer;
    if (print_registers) {
        index = PrintRegisters(index, cpu);
    }
    Reg pc = cpu->getPC();
    UINT8* ip = (UINT8*)ADDR_UINT32(pc);
    char* beginning_of_line = index;
    index += sim_sprintf(index, "%d:%lld:%.16llX DIS  | ", cpu->getID(), global_sim->getSimclock(), pc);

    if (pc != 0) {
#if HAVE_LIBOPCODES
        struct disassemble_info info;
        init_disassemble_info(&info, &index, (fprintf_ftype)dis_sprintf);
        info.mach          = bfd_mach_i386_i386;
        info.buffer        = ip;
        info.buffer_vma    = ADDR_UINT32(pc);
        info.buffer_length = 0xffffffff;
        print_insn_i386(ADDR_UINT32(pc), &info);
#else // !HAVE_LIBOPCODES
        index += sim_sprintf(index, "0x%x,0x%x,0x%x", ip[0], ip[1], ip[2]);
#endif // !HAVE_LIBOPCODES
        // if this is "int $0x0 (INT_Ib with zero immediate) then add hook name from EAX
        if (*ip == 0xCD && cpu->tmp.imm0 == 0) {
            const char* name = isa_ext_name(cpu->InterpCtxt.GPR[EAX]);
            if (name) {
                index += sim_sprintf(index, " %s", name);
            } else {
                index += sim_sprintf(index, " <unknown isa_ext>");
            }
        }
        if (print_symbols) {
            int characters_on_line = index-beginning_of_line;
            int padding = 80-characters_on_line;
            index = Processor::appendSymbolInformation(index, pc, padding);
        }
    }
    index += sim_sprintf(index, "\n");
    
    ASSERT(index-buffer<buffer_size);
    logger->o(severity, "%s", buffer);
}
